#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import socket
import json
import hashlib
import os

BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

#3、定义‘socket连接’类
class SocketClient(object):
    response_codes={
            '200': "pass user authentication",
            '201': "wrong username or password",
            '202': "invalid username or password",
            '300': "Ready to get file from server",
            '301': "Ready to send to  server",
            '302': "File doesn't exist on ftp server",
            '303': "Continue download",
        }
    def __init__(self,args):
        self.args=args
        self.argv_parser()

    #3、定义用户指令parser解析器
    def argv_parser(self):
        if len(self.args) <5:
            sys.exit(self.help_socket())
        else:
            mondatary_fields=["-h","-p"]
            for i in mondatary_fields:
                if i not in self.args:
                    sys.exit("\033[31;1m Lack of argument :[%s] \033[0m"%i)
            try:
                self.ftp_host=self.args[self.args.index("-h")+1]
                self.ftp_port=int(self.args[self.args.index("-p")+1])   #必须是int类型，要不会报socket.meth()方法错误
            except (IndexError,ValueError):
                self.help_socket()
                sys.exit()
            self.connection()
            self.auth()

    def help_socket(self):
        print '''
                usage: myclient.py -h ip_addr -p port
                -h server_ip_address,mandatory
                -p server_port,mandatory
                '''
    def connection(self):
        try:
            self.sk=socket.socket()
            self.sk.connect((self.ftp_host,self.ftp_port))
        except socket.error,e:
            sys.exit("\033[31;1m %s \033[0m"%e)
    def auth(self):
        retry_count=0
        while retry_count<3:
            username=raw_input("username:").strip()
            if len(username)==0:continue
            passwd=raw_input("password:").strip()
            if len(passwd)==0:continue
            input_str=json.dumps({
                'username':username,
                'password':passwd
            })
            send_data="login|%s"%input_str
            self.sk.send(send_data)
            res_data=self.sk.recv(1024)
            res_data_list=res_data.split("|")
            response_code=res_data_list[1]
            print(self.response_codes[response_code])    #对象变量和函数内部变量，虽然名字相同，用法不同；
            if response_code=='200':    #登录成功后的动作
                self.login_user=username
                self.pwd="/"
                self.action()
                return True
            else:
                retry_count+=1  #失败次数+1
        else:
            sys.exit("too many retry counts")   #超过3次锁定用户登录
    def action(self):
        while True:
            action=raw_input("[%s@%s]>"%(self.login_user,self.pwd)).strip() #用户打命令
            if len(action)==0:
                self.help_action()
                continue  #没给动作
            action_list=action.split()  #转换成列表
            func_str=action_list[0]     #指令
            if hasattr(self,func_str):
                func=getattr(self,func_str)
                func(action_list)   #执行相应的方法
            else:
                print "Invalid instructions."

    def help_action(self):
        print '''
        get ftp_file        : download file from ftp server
        put local  remote   : upload local file to remote
        ls                  : list files on ftp server
        cd  path            : change dir on ftp server
        '''

    def get(self,actions):
        if len(actions)==1:
            print "what do you want to download?"
            return
        file_name_list=actions[1:]  #下载多个文件，actions可能大于2，取成一个列表；
        send_data="download|%s"% json.dumps(file_name_list) #将文件列表转换成json格式，再转成服务器认识的格式；
        self.sk.send(send_data)
        for file_name in file_name_list:    #每个文件分别传输一次
            code,file_size,file_md5 = self.sk.recv(1024).split("|")
            total_file_size = int(file_size)
            if code=="300":
                file_path=os.path.join(BASE_DIR,"var",self.login_user,file_name)
                #############断点续传###########
                if os.path.isfile(file_path):
                    self.get_continue(file_name,file_size,file_path)
                    continue
                ################################
                self.sk.send("301")
                received_size = 0
                with open("f:\\"+file_name,"wb")as f:
                    md5_obj=hashlib.md5()   #初始化md5，不要写在循环里
                    while received_size != total_file_size:
                        data=self.sk.recv(4096)
                        ##############MD5校验###########
                        self.sk.send('MD5?')    #防止粘包
                        real_md5=self.sk.recv(1024)
                        # md5_obj=hashlib.md5()
                        md5_obj.update(data)
                        file_md5=md5_obj.hexdigest()
                        if file_md5 == real_md5:
                            msg="yes"
                            self.sk.send(msg)
                        else:
                            msg="no"
                            print "MD5不正确，传输失败"
                            self.sk.send(msg)
                            break
                        ################################
                        received_size+=len(data)
                        f.write(data)
                        # print("recv size:", total_file_size,received_size)
                        ############进度条###########
                        progress_num=received_size*100/total_file_size
                        sys.stdout.write("\r%s%%"%progress_num)
                        sys.stdout.flush()
                        #############################
                    else:
                        pass #在这里进行md5校验也可以，验最后的就行；
                if msg=="no":
                    os.remove("f:\\"+file_name)
            elif code=="302":   #文件不存在
                print(self.response_codes[code])

    def get_continue(self,file_name,file_size,file_path):
        with open(file_path,"rb")as f:
            exist_data_len=len(f.read())
        data="303|exist_data_len"     #断点续传状态码|发送起始位置
        self.sk.send(data)
        received_size = exist_data_len
        with open(file_path,"a")as f:
            md5_obj=hashlib.md5()
            while received_size != file_size:
                data=self.sk.recv(4096)
                received_size+=len(data)
                f.write(data)
                md5_obj.update(data)    #更新md5
                progress_num=received_size*100/file_size    #进度条
                sys.stdout.write("\r%s%%"%progress_num)
                sys.stdout.flush()
            else:
                file_md5=md5_obj.hexdigest()        #MD5可视化
                self.sk.send("real file md5?")
                real_md5=self.sk.recv(1024)         #接收源文件的MD5值
                if file_md5==real_md5:      #对比MD5，这种就是传输后校验
                    print "文件校验成功"
                else:
                    print "文件校验失败"

    def put(self,actions):
        if len(actions)==1:
            print "what do you want to upload?"
            return
        file_name_list=actions[1:]  #下载多个文件，actions可能大于2，取成一个列表；
        send_data="upload|%s"% json.dumps(file_name_list) #将文件列表转换成json格式，再转成服务器认识的格式；
        self.sk.send(send_data)
        self.sk.recv(1024)
        base_dir="f:\\"
        for file_name in file_name_list:
            file_path=os.path.join(base_dir,file_name)
            file_size=os.path.getsize(file_path)
            print file_size
            self.sk.send("%s|%s"%(file_name,file_size))
            send_size=0
            with open(file_path,"rb")as f:
                while send_size != file_size:
                    data=f.read(4096)
                    self.sk.send(data)
                    send_size+=len(data)
                    print("send:",file_size,send_size)
                else:
                    print "upload success."

    def ls(self,actions):   #列出文件名
        cwd=os.getcwd()
        send_data="listfile|%s"%cwd
        self.sk.send(send_data)
        print json.loads(self.sk.recv(1024)) #打印接收结果,list

    def cd(self,actions):
        if len(actions)==1:
            print "what do you want to change dir?"
        if len(actions)>2:
            print "too many arguments"
        else:
            to_dir=actions[1]
            send_data="chrdir|%s"%to_dir
            self.sk.send(send_data)
            print self.sk.recv(1024)    #win上报错;WindowsError: [Error 3]


    def exit(self,actions):
        self.sk.send("exit")
        sys.exit("connet close.")